/* ==========================================
 * JGraphT : a free Java graph-theory library
 * ==========================================
 *
 * Project Info:  http://jgrapht.sourceforge.net/
 * Project Creator:  Barak Naveh (http://sourceforge.net/users/barak_naveh)
 *
 * (C) Copyright 2003-2008, by Barak Naveh and Contributors.
 *
 * This program and the accompanying materials are dual-licensed under
 * either
 *
 * (a) the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation, or (at your option) any
 * later version.
 *
 * or (per the licensee's choosing)
 *
 * (b) the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation.
 */
/* ----------------------
 * JGraphAdapterDemo.java
 * ----------------------
 * (C) Copyright 2003-2008, by Barak Naveh and Contributors.
 *
 * Original Author:  Barak Naveh
 * Contributor(s):   -
 *
 * $Id$
 *
 * Changes
 * -------
 * 03-Aug-2003 : Initial revision (BN);
 * 07-Nov-2003 : Adaptation to JGraph 3.0 (BN);
 *
 */
package org.jgrapht.demo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JFrame;

import khiem.java.sample.genericgrapth.DependencyResolver;

import org.jgraph.JGraph;
import org.jgraph.graph.AttributeMap;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.GraphConstants;
import org.jgrapht.DirectedGraph;
import org.jgrapht.ListenableGraph;
import org.jgrapht.event.GraphEdgeChangeEvent;
import org.jgrapht.event.GraphListener;
import org.jgrapht.event.GraphVertexChangeEvent;
import org.jgrapht.ext.JGraphModelAdapter;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.DefaultListenableGraph;
import org.jgrapht.graph.DirectedMultigraph;

import com.automic.pm.core.PackageDependency;
import com.automic.pm.core.PackageInfo;
import com.automic.pm.core.PackageRepository;
import com.automic.pm.versioning.ComparisonOperator;
import com.automic.pm.versioning.DefaultVersion;
import com.google.common.base.Predicate;
// resolve ambiguity
import com.google.common.collect.Sets;


/**
 * A demo applet that shows how to use JGraph to visualize JGraphT graphs.
 *
 * @author Barak Naveh
 * @since Aug 3, 2003
 */
public class JGraphAdapterDemo
    extends JApplet
{
    

    private static final long serialVersionUID = 3256444702936019250L;
    private static final Color DEFAULT_BG_COLOR = Color.decode("#FAFBFF");
    private static final Dimension DEFAULT_SIZE = new Dimension(1030, 820);

    

    private PackageRepository<PackageInfo> graph;
    
    private PackageInfo a = new PackageInfo("a", new DefaultVersion("2.0.0"), new ArrayList<PackageDependency>());
    private PackageInfo b = new PackageInfo("b", new DefaultVersion("2.0.1"), new ArrayList<PackageDependency>());
    private PackageInfo c = new PackageInfo("c", new DefaultVersion("3.1.1"), new ArrayList<PackageDependency>());
    private PackageInfo d31 = new PackageInfo("d", new DefaultVersion("3.1.0"), new ArrayList<PackageDependency>());
    private PackageInfo d32 = new PackageInfo("d", new DefaultVersion("3.2.0"), new ArrayList<PackageDependency>());
    private PackageInfo d33 = new PackageInfo("d", new DefaultVersion("3.3.0"), new ArrayList<PackageDependency>());
    private PackageInfo d34 = new PackageInfo("d", new DefaultVersion("3.4.0"), new ArrayList<PackageDependency>());
    private PackageInfo d35 = new PackageInfo("d", new DefaultVersion("3.5.0"), new ArrayList<PackageDependency>());
    private PackageInfo e = new PackageInfo("e", new DefaultVersion("5.3.0"), new ArrayList<PackageDependency>());
    private PackageInfo f15 = new PackageInfo("f", new DefaultVersion("1.5.0"), new ArrayList<PackageDependency>());
    private PackageInfo f17 = new PackageInfo("f", new DefaultVersion("1.7.0"), new ArrayList<PackageDependency>());
    private PackageInfo h23 = new PackageInfo("h", new DefaultVersion("2.3.1"), new ArrayList<PackageDependency>());
    private PackageInfo h22 = new PackageInfo("h", new DefaultVersion("2.2.1"), new ArrayList<PackageDependency>());
    
    private PackageInfo f13 = new PackageInfo("f", new DefaultVersion("1.3.0"), new ArrayList<PackageDependency>());
    private PackageInfo b10 = new PackageInfo("b", new DefaultVersion("1.0.0"), new ArrayList<PackageDependency>());
    private PackageInfo d21 = new PackageInfo("d", new DefaultVersion("2.1.0"), new ArrayList<PackageDependency>());
    
    //private PackageInfo z = new PackageInfo("z", new DefaultVersion("1.0.0"), new ArrayList<PackageDependency>());
            
    // create a JGraphT graph
    ListenableGraph<PackageInfo, DefaultEdge> visualGraph = new ListenableDirectedMultigraph(DefaultEdge.class);

    // create a visualization using JGraph, via an adapter
    private JGraphModelAdapter<PackageInfo, DefaultEdge> jgAdapter = new JGraphModelAdapter<PackageInfo, DefaultEdge>(visualGraph);

    JGraph jgraph = new JGraph(jgAdapter);
    JButton resolveButton = new JButton("Determine dependence");
    
    DependencyResolver resolver;
    List<Set<PackageInfo>> intersections = new ArrayList<Set<PackageInfo>>();
    Set<PackageInfo> invalids;

    /**
     * An alternative starting point for this demo, to also allow running this
     * applet as an application.
     *
     * @param args ignored.
     */
    public static void main(String [] args)
    {
        JGraphAdapterDemo applet = new JGraphAdapterDemo();
        applet.init();
        JFrame frame = new JFrame();
        frame.getContentPane().add(applet);
        frame.setTitle("JGraphT Adapter to JGraph Demo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
        
    }
    
    public void findInvalidNodes() {
        resolver = new DependencyResolver(graph); 
        resolver.tryResolving(a, 0);
        
        Set<PackageInfo> valids = resolver.getValid();
                
        invalids = Sets.newHashSet(Sets.difference(visualGraph.vertexSet(), valids));
                    
        for (PackageInfo invalid : invalids) {
            DefaultGraphCell cell = jgAdapter.getVertexCell(invalid);
            AttributeMap attr = cell.getAttributes();
            GraphConstants.setBackground(attr, Color.GRAY);
            AttributeMap cellAttr = new AttributeMap();
            cellAttr.put(cell, attr);
            jgAdapter.edit(cellAttr, null, null, null);
        }
        
        Set<List<PackageInfo>> circulars = resolver.circulars;
        if (circulars.size() > 0) {
            for (List<PackageInfo> circle : circulars) {
                for (int i = 0; i< circle.size(); i++ ) {
                    if (i == circle.size() -1) {
                        DefaultEdge edge = visualGraph.getEdge(circle.get(i), circle.get(0));
                        org.jgraph.graph.DefaultEdge de = jgAdapter.getEdgeCell(edge);
                        AttributeMap attr = de.getAttributes();
                        GraphConstants.setLineColor(attr, Color.RED);
                        AttributeMap cellAttr = new AttributeMap();
                        cellAttr.put(de, attr);
                        jgAdapter.edit(cellAttr, null, null, null);
                    } else {
                        DefaultEdge edge = visualGraph.getEdge(circle.get(i), circle.get(i+1));
                        org.jgraph.graph.DefaultEdge de = jgAdapter.getEdgeCell(edge);
                        AttributeMap attr = de.getAttributes();
                        GraphConstants.setLineColor(attr, Color.RED);
                        AttributeMap cellAttr = new AttributeMap();
                        cellAttr.put(de, attr);
                        jgAdapter.edit(cellAttr, null, null, null);
                        
                    }
                }
            }
        }
        
        if (valids.contains(a)) {
            resolveButton.setText("Remove invalid nodes");
            resolveButton.removeActionListener(resolveButton.getActionListeners()[0]);
            resolveButton.addActionListener(new ActionListener() {
                
                @Override
                public void actionPerformed(ActionEvent e) {
                    removeInvalidNodes();
                    repaint();
                }
            });
        }
    }
    
    public void removeInvalidNodes() {
        Set<PackageInfo> valids = resolver.getValid();
        
        for (PackageInfo vertex : visualGraph.vertexSet()) {
            if (!valids.contains(vertex)) {
                Set<DefaultEdge> edges = visualGraph.edgesOf(vertex);
                if (edges.size() > 0) {
                    visualGraph.removeAllEdges(edges);
                }            
            }
        }
                            
        visualGraph.removeAllVertices(invalids);
        resolveButton.setText("Find intersections");
        resolveButton.removeActionListener(resolveButton.getActionListeners()[0]);
        resolveButton.addActionListener(new ActionListener() {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                findIntersections();
                repaint();
            }
        });
    }
    
    public void findIntersections() {        
        boolean result = resolver.findIntersections(intersections);
        
        for (PackageInfo node : visualGraph.vertexSet()) {
            if (node.equals(a)) continue;
            DefaultGraphCell cell = jgAdapter.getVertexCell(node);
            AttributeMap attr = cell.getAttributes();
            GraphConstants.setBackground(attr, Color.GRAY);
            AttributeMap cellAttr = new AttributeMap();
            cellAttr.put(cell, attr);
            jgAdapter.edit(cellAttr, null, null, null);
        }
        
        
        // highlisght intersections
        Color[] colors = new Color[] {Color.BLUE, Color.CYAN, Color.MAGENTA,  Color.YELLOW, Color.PINK, Color.LIGHT_GRAY, Color.ORANGE, Color.getColor("brown"), Color.RED};
        int i = 0;
        for (Set<PackageInfo> intersection : intersections) {
            for (PackageInfo node : intersection) {
                DefaultGraphCell cell = jgAdapter.getVertexCell(node);
                AttributeMap attr = cell.getAttributes();
                GraphConstants.setBackground(attr, colors[i]);
                AttributeMap cellAttr = new AttributeMap();
                cellAttr.put(cell, attr);
                jgAdapter.edit(cellAttr, null, null, null);
            }
            i++;
        }
        
        if (result) {
            resolveButton.setText("Remove invalid nodes");
            resolveButton.removeActionListener(resolveButton.getActionListeners()[0]);
            resolveButton.addActionListener(new ActionListener() {
                
                @Override
                public void actionPerformed(ActionEvent e) {
                    removeInvalidNodes2();
                    repaint();
                }
            });
        }
    }
    
    public void removeInvalidNodes2() {
        Set<PackageInfo> valids = resolver.getValid();
        for (final Set<PackageInfo> intersection : intersections) {
            final String nodeName = intersection.iterator().next().getName();
            valids = Sets.filter(valids, new Predicate<PackageInfo>() {
                @Override
                public boolean apply(PackageInfo arg0) {
                    return (!arg0.getName().equals(nodeName) || intersection.contains(arg0));
                }
            });
        }
        for (PackageInfo vertex : visualGraph.vertexSet()) {
            if (!valids.contains(vertex)) {
                Set<DefaultEdge> edges = visualGraph.edgesOf(vertex);
                if (edges.size() > 0) {
                    visualGraph.removeAllEdges(edges);
                }            
            }
        }
        
        invalids = Sets.newHashSet(Sets.difference(visualGraph.vertexSet(), valids));
                    
        visualGraph.removeAllVertices(invalids);
        resolveButton.setText("Choose best versions");
        resolveButton.removeActionListener(resolveButton.getActionListeners()[0]);
        resolveButton.addActionListener(new ActionListener() {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                chooseBestVersions();
                repaint();
            }
        });
    }
    
    public void chooseBestVersions() {
        boolean result = resolver.chooseBestVersions(intersections);
        Set<PackageInfo> valids = resolver.getValid();
        for (PackageInfo vertex : visualGraph.vertexSet()) {
            if (!valids.contains(vertex)) {
                DefaultGraphCell cell = jgAdapter.getVertexCell(vertex);
                AttributeMap attr = cell.getAttributes();
                GraphConstants.setBackground(attr, Color.GRAY);
                AttributeMap cellAttr = new AttributeMap();
                cellAttr.put(cell, attr);
                jgAdapter.edit(cellAttr, null, null, null);        
            }
        }
        
        if(result) {
            resolveButton.setText("Final resolution");
            resolveButton.removeActionListener(resolveButton.getActionListeners()[0]);
            resolveButton.addActionListener(new ActionListener() {
                
                @Override
                public void actionPerformed(ActionEvent e) {
                    finalResolution();
                    repaint();
                }
            });
        }
    }
    
    public void finalResolution() {
        Set<PackageInfo> valids = resolver.getValid();
        for (PackageInfo vertex : visualGraph.vertexSet()) {
            if (!valids.contains(vertex)) {
                Set<DefaultEdge> edges = visualGraph.edgesOf(vertex);
                if (edges.size() > 0) {
                    visualGraph.removeAllEdges(edges);
                }         
            }
        }
        
        invalids = Sets.newHashSet(Sets.difference(visualGraph.vertexSet(), valids));
        
        visualGraph.removeAllVertices(invalids);
        
        List<PackageInfo> installeds = graph.getInstalledNodes();
        for (PackageInfo vertex : visualGraph.vertexSet()) {
            DefaultGraphCell cell = jgAdapter.getVertexCell(vertex);
            AttributeMap attr = cell.getAttributes();
            if (installeds.contains(vertex)) {                 
                GraphConstants.setBackground(attr, Color.GREEN);
            } else {
                if (graph.isInstalled(vertex.getName())) {
                    GraphConstants.setBackground(attr, Color.ORANGE);
                } else {
                    GraphConstants.setBackground(attr, Color.PINK);
                }
            }
            
            AttributeMap cellAttr = new AttributeMap();
            cellAttr.put(cell, attr);
            jgAdapter.edit(cellAttr, null, null, null);        
        }
    }

    /**
     * {@inheritDoc}
     */
    public void init()
    {      
        adjustDisplaySettings(jgraph);
        getContentPane().setLayout(new FlowLayout());
        getContentPane().add(jgraph);
        getContentPane().add(resolveButton);
        resize(DEFAULT_SIZE);
        
        
                
        graph = new PackageRepository<>();      
        a.getPackageDependencies().add(new PackageDependency("b", ComparisonOperator.APPROXIMATELY_GREATER_THAN, new DefaultVersion("2.0")));
        a.getPackageDependencies().add(new PackageDependency("c", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("3.0")));
        a.getPackageDependencies().add(new PackageDependency("h", ComparisonOperator.APPROXIMATELY_GREATER_THAN, new DefaultVersion("2.0")));
        b.getPackageDependencies().add(new PackageDependency("d", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("3.2")
            , ComparisonOperator.LESS_THAN_OR_EQUAL_TO, new DefaultVersion("4.0")));
        c.getPackageDependencies().add(new PackageDependency("d", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("3.0.0")
            , ComparisonOperator.LESS_THAN_OR_EQUAL_TO, new DefaultVersion("3.4.0")));
        
        d31.getPackageDependencies().add(new PackageDependency("e", ComparisonOperator.APPROXIMATELY_GREATER_THAN, new DefaultVersion("5.0")));
        d32.getPackageDependencies().add(new PackageDependency("e", ComparisonOperator.APPROXIMATELY_GREATER_THAN, new DefaultVersion("5.1")));
        d33.getPackageDependencies().add(new PackageDependency("e", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("5.7")));
        d34.getPackageDependencies().add(new PackageDependency("f", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("1.0")));
        d35.getPackageDependencies().add(new PackageDependency("f", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("1.0")));
        f17.getPackageDependencies().add(new PackageDependency("g", ComparisonOperator.APPROXIMATELY_GREATER_THAN, new DefaultVersion("2.0")));
        h22.getPackageDependencies().add(new PackageDependency("f", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("1.0")));
        h23.getPackageDependencies().add(new PackageDependency("f", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("1.6")));
        e.getPackageDependencies().add(new PackageDependency("b", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO , new DefaultVersion("2.0.0")));
        
        graph.addAll(a, b, c, d31, d32, d33, d34, d35, e, f15, f17, h23, h22);
        
        // add some installed nodes
        b10.getPackageDependencies().add(new PackageDependency("d", ComparisonOperator.GREATER_THAN_OR_EQUAL_TO, new DefaultVersion("2.0")));
        //z.addDependency("d", ">=", "3.5");
        
        graph.addInstalledNodes(f13, b10, d21);
        
        final List<PackageInfo> installeds = graph.getInstalledNodes();
        
        visualGraph.addGraphListener(new GraphListener<PackageInfo, DefaultEdge>() {
            
            @Override
            public void vertexRemoved(GraphVertexChangeEvent<PackageInfo> arg0) {
                // TODO Auto-generated method stub
                
            }
            
            @Override
            public void vertexAdded(GraphVertexChangeEvent<PackageInfo> arg0) {
                PackageInfo vertex = arg0.getVertex();
                DefaultGraphCell cell = jgAdapter.getVertexCell(vertex);
                AttributeMap attr = cell.getAttributes();
                if (installeds.contains(vertex)) {                 
                    GraphConstants.setBackground(attr, Color.GREEN);
                } else {
                    if (graph.isInstalled(vertex.getName())) {
                        GraphConstants.setBackground(attr, Color.ORANGE);
                    }
                }
            }
            
            @Override
            public void edgeRemoved(GraphEdgeChangeEvent<PackageInfo, DefaultEdge> arg0) {
                // TODO Auto-generated method stub
                
            }
            
            @Override
            public void edgeAdded(GraphEdgeChangeEvent<PackageInfo, DefaultEdge> arg0) {
                // TODO Auto-generated method stub
                
            }
        });

        for (PackageInfo pkg : graph.getNodes()) {
            visualGraph.addVertex(pkg);
        }
        
        for (PackageInfo pkg : graph.getInstalledNodes()) {
            visualGraph.addVertex(pkg);
        }
        
        resolveButton.addActionListener(new ActionListener() {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                
                for (PackageInfo vertex : visualGraph.vertexSet()) {
                    List<PackageDependency> deps = vertex.getPackageDependencies();
                    for (PackageDependency dep : deps) {
                        for (PackageInfo otherVertext : visualGraph.vertexSet()) {
                            if (dep.validate(otherVertext)) {
                                visualGraph.addEdge(vertex, otherVertext);
                            }
                        }
                    }
                }
                
                resolveButton.setText("Traverse graph");
                resolveButton.removeActionListener(resolveButton.getActionListeners()[0]);
                resolveButton.addActionListener(new ActionListener() {
                    
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        findInvalidNodes();   
                        repaint();
                    }
                });            
                
            }
        });
        
        
                
        // position vertices nicely within JGraph component
        positionVertexAt(a, 400, 100);
        
        positionVertexAt(b, 60, 250);
        positionVertexAt(c, 310, 250);
        
        positionVertexAt(d31, 80, 500);
        positionVertexAt(d32, 200, 500);
        positionVertexAt(d33, 400, 500);
        positionVertexAt(d34, 650, 500);
        positionVertexAt(d35, 800, 500);
        
        positionVertexAt(e, 70, 700);
        positionVertexAt(f13, 300, 700);
        positionVertexAt(f15, 500, 700);
        positionVertexAt(f17, 700, 700);
        
        positionVertexAt(b10, 570, 250);
        
        positionVertexAt(d21, 400, 350);
        positionVertexAt(h22, 40, 350);
        positionVertexAt(h23, 800, 350);
        
        //positionVertexAt(z, 700, 100);

        // that's all there is to it!...
    }

    private void adjustDisplaySettings(JGraph jg)
    {
        jg.setPreferredSize(DEFAULT_SIZE);

        Color c = DEFAULT_BG_COLOR;
        String colorStr = null;

        try {
            colorStr = getParameter("bgcolor");
        } catch (Exception e) {
        }

        if (colorStr != null) {
            c = Color.decode(colorStr);
        }

        jg.setBackground(c);
    }

    @SuppressWarnings("unchecked") // FIXME hb 28-nov-05: See FIXME below
    private void positionVertexAt(Object vertex, int x, int y)
    {
        DefaultGraphCell cell = jgAdapter.getVertexCell(vertex);
        AttributeMap attr = cell.getAttributes();
        Rectangle2D bounds = GraphConstants.getBounds(attr);
        

        Rectangle2D newBounds =
            new Rectangle2D.Double(
                x,
                y,
                bounds.getWidth(),
                bounds.getHeight());

        GraphConstants.setBounds(attr, newBounds);
                
        // TODO: Clean up generics once JGraph goes generic
        AttributeMap cellAttr = new AttributeMap();
        cellAttr.put(cell, attr);
        jgAdapter.edit(cellAttr, null, null, null);
    }

    

    /**
     * a listenable directed multigraph that allows loops and parallel edges.
     */
    private static class ListenableDirectedMultigraph<V, E>
        extends DefaultListenableGraph<V, E>
        implements DirectedGraph<V, E>
    {
        private static final long serialVersionUID = 1L;

        ListenableDirectedMultigraph(Class<E> edgeClass)
        {
            super(new DirectedMultigraph<V, E>(edgeClass));
        }
    }
}

// End JGraphAdapterDemo.java
